class_name Player
extends CharacterBody2D

signal interact

var moveVector = Vector2.ZERO
@export var maxHorizontalSpeed = 100
@export var horizontalAccelSpeed = 600
@export var jumpSpeed = 300
@export var jumpNum: int = 2
@export var maxFallSpeed = 300
var jumpAvai: int
@export var delayJump: float = 0.3
var delayJumpTimer: float
@export var gravity = 900
@export var sceneDrag = 800
var sceneDragForce = Vector2.ZERO
var direction = 1

var isOnFloor: bool

@export var maxDashSpeed = 400
@export var dashAccelSpeed = 250
@export var maxDashDistance = 45
@export var airDashNum: int = 1
var airDashAvai: int
var hasDash = true
var isDashing = false
var dashDistance: float

enum jumpStates {
	NONE,
	NORMAL,
	WALL
}
var currentJumpState: jumpStates

# Wall Detector
var collider
var isOnWall: bool
var hasWallJump: bool

# unhandeled inputs
var input_jump: bool = false
var input_dash: bool = false

var is_protected: bool = false
var is_dead: bool = false
enum EnteringMode {
	NORMAL,
	UP,
	DOWN,
}
var is_entering: bool = false
@export var ENTERING_DISTANCE: float = 30

@onready var weapon_player: AnimationPlayer = $Weapon/AnimationPlayer
@onready var freeze_player_timer: Timer = $FreezePlayerTimer
@onready var teleport_player: AnimationPlayer = $Effects/Teleport/AnimationPlayer
@onready var dead_player: AnimationPlayer = $Effects/Dead/AnimationPlayer
@onready var knock_damage_timer: Timer = $KnockDamageTimer
@onready var protection_timer: Timer = $ProtectionTimer
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var virtual_joypad: CanvasLayer = $VirtualJoypad
@onready var game_over_screen: CanvasLayer = $GameOverScreen


func _ready() -> void:
	collider = $RayCast2D
	add_to_group("player")
	GlobalGameData.player_dead.connect(dead)
	virtual_joypad.show()


func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("attack"):
		weapon_player.play("attack")
	if event.is_action_pressed("interact"):
		interact.emit()


func freeze() -> void:
	freeze_player_timer.start()
	isDashing = false
	$DashFreezeTimer.start()
	moveVector = Vector2.ZERO
	velocity.x = 0


func enter_new_scene(direction: int, mode: EnteringMode) -> void:
	if mode == EnteringMode.DOWN:
		velocity.y = maxFallSpeed
		await isOnFloor
		GlobalGameData.playerRespawnPoints.append({
			"pos": global_position,
			"name": "INIT"
		})
		return
	
	
	set_collision_layer_value(3, false)
	is_entering = true
	var tween := create_tween()
	tween.tween_property(self, ^"global_position:x",
		global_position.x + ENTERING_DISTANCE * direction,
		ENTERING_DISTANCE / maxHorizontalSpeed
	)
	
	if mode == EnteringMode.UP:
		moveVector.y = -jumpSpeed
	await  tween.finished
	set_collision_layer_value(3, true)
	GlobalGameData.playerRespawnPoints.append({
		"pos": global_position,
		"name": "INIT"
	})
	is_entering = false


func move(delta):
	moveVector.y = velocity.y
	
	# 设置有关土狼跳真实离地状态
	isOnFloor = is_on_floor()
	isOnWall = collider.is_colliding() and not isOnFloor and hasWallJump
	
	if delayJumpTimer > 0:
		isOnFloor = true
	
	# 接受左右移动事件
	moveVector.x = Input.get_action_strength("movement_right") - Input.get_action_strength("movement_left")
	velocity.x += delta * moveVector.x * horizontalAccelSpeed  # 实际应用到物理引擎的velocity
	if moveVector.x == 0:
		velocity.x = lerp(0.0, velocity.x, pow(2, -40 * delta))  # 缓慢减速
	
	# Dashing
	if Input.is_action_just_pressed("movement_dash") and hasDash and airDashAvai > 0:
		if !isOnFloor:
			airDashAvai -= 1
		if isOnWall:
			direction = -direction
		hasDash = false
		isDashing = true
		dashDistance = 0
		moveVector.x = dashAccelSpeed * direction
	
	# 贴墙下落时减速
	if isOnWall and velocity.y > 0:
		sceneDragForce.y = sceneDrag
	else:
		sceneDragForce.y = 0
	
	# 在地面上时重置跳跃和冲刺的状态
	if isOnFloor:
		jumpAvai = jumpNum - 1
		airDashAvai = airDashNum
		currentJumpState = jumpStates.NONE
	
	# 跳跃
	if Input.is_action_just_pressed("movement_jump") and ((jumpAvai > 0 and !isOnFloor) or isOnFloor):
		if !isOnFloor:
			jumpAvai -= 1
		moveVector.y = -jumpSpeed  # 在godot中负velocity为上
		delayJumpTimer = 0
		currentJumpState = jumpStates.NORMAL
		if isOnWall:
			direction = -direction
			currentJumpState = jumpStates.WALL
	
	if currentJumpState == jumpStates.WALL:
		velocity.x = direction * maxHorizontalSpeed
	
	if Input.is_action_just_released("movement_jump") and moveVector.y < 0:
		moveVector.y = 0
		currentJumpState = jumpStates.NONE
	if moveVector.y >= 0 and currentJumpState != jumpStates.NONE:
		currentJumpState = jumpStates.NONE


func dash(delta: float) -> void:
	moveVector.x += dashAccelSpeed * delta * direction
	moveVector.x = clamp(moveVector.x, -maxDashSpeed, maxDashSpeed)
	dashDistance += abs(moveVector.x * delta)
	if dashDistance > maxDashDistance:
		moveVector.x -= (dashDistance - maxDashDistance) * direction
		isDashing = false
		moveVector.y = 0
		$DashFreezeTimer.start()
	velocity.x = moveVector.x

func resetPlayerToFloor():
	velocity.y = 100
	move_and_slide()

func _physics_process(delta: float) -> void:
	if freeze_player_timer.time_left > 0:
		velocity.y = 100
		move_and_slide()
		return
	
	if knock_damage_timer.time_left > 0:
		knock_on_damage()
		return
	
	if !isDashing:
		if not is_entering:
			move(delta)
	else:
		dash(delta)
	
	
	if isDashing:
		velocity.y = 0
	else:
		velocity.x = clamp(velocity.x, -maxHorizontalSpeed, maxHorizontalSpeed)
		velocity.y = moveVector.y
		velocity.y += delta * (gravity - sceneDragForce.y)
		if velocity.y > maxFallSpeed * (1 - sceneDragForce.y / gravity):
			velocity.y = maxFallSpeed * (1 - sceneDragForce.y / gravity)
		if is_entering:
			moveVector.y = velocity.y
	
	if is_on_floor() or isOnWall:
		delayJumpTimer = delayJump
	else:
		delayJumpTimer += -delta
		#delayJumpTimer = clamp(delayJumpTimer, delayJump, 0)
		if delayJumpTimer < 0:
			delayJumpTimer = 0
			
	move_and_slide()


func _process(delta: float) -> void:
	update_ability()
	
	if velocity.y == 0:
		if abs(velocity.x) <= 1:
			$AnimatedSprite2D.animation = "idle"
		else:
			$AnimatedSprite2D.animation = "walk"
		
	else:
		if velocity.y > 0:
			$AnimatedSprite2D.animation = "fall"
		else:
			$AnimatedSprite2D.animation = "jump"
	
	if is_entering:
		animated_sprite_2d.animation = "walk"
	
	collider.target_position = Vector2(9 * direction, 0)
	if velocity.x > 0:
		$AnimatedSprite2D.flip_h = false
		direction = 1
	if velocity.x < 0:
		$AnimatedSprite2D.flip_h = true
		direction = -1
	
	$AnimatedSprite2D.play()
	
	$Weapon.scale.x = direction
	
	if teleport_player.is_playing():
		teleport_player.get_parent().show()
	else:
		teleport_player.get_parent().hide()
	
	if dead_player.is_playing():
		dead_player.get_parent().show()
	else:
		dead_player.get_parent().hide()
	
	if protection_timer.time_left > 0:
		modulate.a = sin(Engine.get_process_frames() * delta * 70) * 0.5 + 0.5
		is_protected = true
	if protection_timer.is_stopped():
		modulate.a = 1
		is_protected = false
	


func _on_dash_freeze_timer_timeout() -> void:
	hasDash = true


func init_knock_back_on_damage() -> void:
	velocity = Vector2(-direction * 300, -100)
	knock_damage_timer.start()
	protection_timer.start()


func knock_on_damage() -> void:
	velocity.y = move_toward(velocity.y, -30.0, 1)
	velocity.x = move_toward(velocity.x, 15.0, 25)
	move_and_slide()


func dead() -> void:
	set_collision_layer_value(3, false)
	is_dead = true
	moveVector = Vector2.ZERO
	velocity = moveVector
	dead_player.play("dead")
	protection_timer.stop()
	knock_damage_timer.stop()
	freeze_player_timer.start()
	freeze_player_timer.paused = true
	
	await dead_player.animation_finished
	hide()


func show_game_over_screen() -> void:
	game_over_screen.show_game_over_screen()


func update_ability() -> void:
	if GlobalGameData.Abilities.DASH in GlobalGameData.player_abilities:
		airDashNum = 1
	else:
		airDashNum = 0
	
	if GlobalGameData.Abilities.DOUBLE_JUMP in GlobalGameData.player_abilities:
		jumpNum = 2
	else:
		jumpNum = 1
	
	if GlobalGameData.Abilities.WALL_JUMP in GlobalGameData.player_abilities:
		hasWallJump = true
	else:
		hasWallJump = false
